/**
 * $RCSfile$
 * $Revision: 2779 $
 * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $
 *
 * Copyright 2003-2006 Jive Software.
 *
 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.smackx.muc;

import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;

import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A <code>RoomListenerMultiplexor</code> multiplexes incoming packets on a
 * <code>Connection</code> using a single listener/filter pair. A single
 * <code>RoomListenerMultiplexor</code> is created for each
 * {@link org.jivesoftware.smack.Connection} that has joined MUC rooms within
 * its session.
 * 
 * @author Larry Kirschner
 */
class RoomListenerMultiplexor implements ConnectionListener {

	// We use a WeakHashMap so that the GC can collect the monitor when the
	// connection is no longer referenced by any object.
	private static final Map<Connection, WeakReference<RoomListenerMultiplexor>> monitors = new WeakHashMap<Connection, WeakReference<RoomListenerMultiplexor>>();

	private Connection connection;
	private RoomMultiplexFilter filter;
	private RoomMultiplexListener listener;

	/**
	 * Returns a new or existing RoomListenerMultiplexor for a given connection.
	 * 
	 * @param conn
	 *            the connection to monitor for room invitations.
	 * @return a new or existing RoomListenerMultiplexor for a given connection.
	 */
	public static RoomListenerMultiplexor getRoomMultiplexor(Connection conn) {
		synchronized (monitors) {
			if (!monitors.containsKey(conn) || monitors.get(conn).get() == null) {
				RoomListenerMultiplexor rm = new RoomListenerMultiplexor(conn,
						new RoomMultiplexFilter(), new RoomMultiplexListener());

				rm.init();

				// We need to use a WeakReference because the monitor references
				// the
				// connection and this could prevent the GC from collecting the
				// monitor
				// when no other object references the monitor
				monitors.put(conn, new WeakReference<RoomListenerMultiplexor>(
						rm));
			}
			// Return the InvitationsMonitor that monitors the connection
			return monitors.get(conn).get();
		}
	}

	/**
	 * All access should be through the static method
	 * {@link #getRoomMultiplexor(Connection)}.
	 */
	private RoomListenerMultiplexor(Connection connection,
			RoomMultiplexFilter filter, RoomMultiplexListener listener) {
		if (connection == null) {
			throw new IllegalArgumentException("Connection is null");
		}
		if (filter == null) {
			throw new IllegalArgumentException("Filter is null");
		}
		if (listener == null) {
			throw new IllegalArgumentException("Listener is null");
		}
		this.connection = connection;
		this.filter = filter;
		this.listener = listener;
	}

	public void addRoom(String address, PacketMultiplexListener roomListener) {
		filter.addRoom(address);
		listener.addRoom(address, roomListener);
	}

	public void connectionClosed() {
		cancel();
	}

	public void connectionClosedOnError(Exception e) {
		cancel();
	}

	public void reconnectingIn(int seconds) {
		// ignore
	}

	public void reconnectionSuccessful() {
		// ignore
	}

	public void reconnectionFailed(Exception e) {
		// ignore
	}

	/**
	 * Initializes the listeners to detect received room invitations and to
	 * detect when the connection gets closed. As soon as a room invitation is
	 * received the invitations listeners will be fired. When the connection
	 * gets closed the monitor will remove his listeners on the connection.
	 */
	public void init() {
		connection.addConnectionListener(this);
		connection.addPacketListener(listener, filter);
	}

	public void removeRoom(String address) {
		filter.removeRoom(address);
		listener.removeRoom(address);
	}

	/**
	 * Cancels all the listeners that this InvitationsMonitor has added to the
	 * connection.
	 */
	private void cancel() {
		connection.removeConnectionListener(this);
		connection.removePacketListener(listener);
	}

	/**
	 * The single <code>Connection</code>-level <code>PacketFilter</code> used
	 * by a {@link RoomListenerMultiplexor} for all muc chat rooms on an
	 * <code>Connection</code>. Each time a muc chat room is added to/removed
	 * from an <code>Connection</code> the address for that chat room is added
	 * to/removed from that <code>Connection</code>'s
	 * <code>RoomMultiplexFilter</code>.
	 */
	private static class RoomMultiplexFilter implements PacketFilter {

		private Map<String, String> roomAddressTable = new ConcurrentHashMap<String, String>();

		public boolean accept(Packet p) {
			String from = p.getFrom();
			if (from == null) {
				return false;
			}
			return roomAddressTable.containsKey(StringUtils.parseBareAddress(
					from).toLowerCase());
		}

		public void addRoom(String address) {
			if (address == null) {
				return;
			}
			roomAddressTable.put(address.toLowerCase(), address);
		}

		public void removeRoom(String address) {
			if (address == null) {
				return;
			}
			roomAddressTable.remove(address.toLowerCase());
		}
	}

	/**
	 * The single <code>Connection</code>-level <code>PacketListener</code> used
	 * by a {@link RoomListenerMultiplexor} for all muc chat rooms on an
	 * <code>Connection</code>. Each time a muc chat room is added to/removed
	 * from an <code>Connection</code> the address and listener for that chat
	 * room are added to/removed from that <code>Connection</code>'s
	 * <code>RoomMultiplexListener</code>.
	 * 
	 * @author Larry Kirschner
	 */
	private static class RoomMultiplexListener implements PacketListener {

		private Map<String, PacketMultiplexListener> roomListenersByAddress = new ConcurrentHashMap<String, PacketMultiplexListener>();

		public void processPacket(Packet p) {
			String from = p.getFrom();
			if (from == null) {
				return;
			}

			PacketMultiplexListener listener = roomListenersByAddress
					.get(StringUtils.parseBareAddress(from).toLowerCase());

			if (listener != null) {
				listener.processPacket(p);
			}
		}

		public void addRoom(String address, PacketMultiplexListener listener) {
			if (address == null) {
				return;
			}
			roomListenersByAddress.put(address.toLowerCase(), listener);
		}

		public void removeRoom(String address) {
			if (address == null) {
				return;
			}
			roomListenersByAddress.remove(address.toLowerCase());
		}
	}
}
